<template>
  <div class="out-box" @contextmenu.stop.prevent>
    <div class="toolbox" ref="toolbox">
      <slot name="toolbox" :state="{scaleType,isClip,markShow,pointerType}"></slot>
    </div>
    <div ref="inBox" class="in-box" @contextmenu="exitClip">
      <div
        class="container"
        :id="'container'+ref"
        :style="containerStyle"></div>
      <div class="scrollbar v-scrollbar" ref="vScrollbar" v-show="scrollbar.v" v-if="layer">
        <div class="thumb v-thumb" @mousedown="regulateProgress('v',$event)" :style="{top:`${progress*100}%`}"
             :tip="`${currentPage+1}/${totalCount}页`"></div>
      </div>
      <div class="scrollbar h-scrollbar" ref="hScrollbar" v-show="scrollbar.h">
        <div class="thumb h-thumb" @mousedown="regulateProgress('h',$event)"
             :style="{left:`${currentLeft/totalWidth*100+50}%`}"></div>
      </div>
      <div class="page-mask" v-if="isClip">
        <div class="page-mask-item top"
             :style="{height:maskBound.y+'px',width:`calc(${maskBound.x}px + ${maskBound.width}px)`}"></div>
        <div class="page-mask-item right"
             :style="{width:`calc(100vw - ${maskBound.x}px - ${maskBound.width}px)`,height:`calc(${maskBound.y}px + ${maskBound.height}px)`}"></div>
        <div class="page-mask-item bottom"
             :style="{height:`calc(100vh - ${maskBound.y}px - ${maskBound.height}px)`,width:`calc(100vw - ${maskBound.x}px)`}"></div>
        <div class="page-mask-item left"
             :style="{width:maskBound.x+'px',height:`calc(100vh - ${maskBound.y}px)`}"></div>
      </div>
      <div class="clip-tool" ref="clipToolbar">
        <div class="clip-tool-item" @click="handleClipToolItemClick(item,$event)"
             v-for="(item,index) in clipToolbar" :key="index">{{item.label}}
        </div>
        <div class="clip-tool-item" @click="handleClipToolItemClick({click:cancelClip},$event)">取消</div>
        <div class="clip-tool-item" @click="handleClipToolItemClick({click:completeClip},$event)">完成</div>
      </div>
    </div>
    <div class="loading-mask" v-if="loading">
      <svg class="circular" viewBox="25 25 50 50">
        <circle class="path" cx="50" cy="50" r="20" fill="none"/>
      </svg>
    </div>
  </div>
</template>

<script>
  import uuid from 'uuid/v4'
  import Konva from 'konva'
  import MyResizeObserver from '../util/MyResizeObserver'
  import getPos from '../util/getPos'

  let flagImg
  const img = new Image()
  img.src = require('../img/flag.png')
  img.onload = () => {
    flagImg = new Konva.Image({
      x: 0,
      y: 0,
      height: 80,
      width: 80,
      image: img
    })
    flagImg.cache()
  }

  const DEBUG = false

  function log () {
    if (DEBUG) console.log(...arguments)
  }

  export default {
    name: 'Marking',
    data () {
      return {
        loading: false,
        ref: uuid(),
        scrollbar: {
          v: false, // 垂直滚动条
          h: false // 水平滚动条
        },

        isClip: false,
        markShow: false,

        imageSize: {width: 0, height: 0},
        loadArg: null,

        markList: [], // 需要复现的选区列表
        dataList: [], // 数据列表
        imageList: {}, // 缓存后的图片列表，键是一系列连续的数字

        pageLoadCount: 3, // 页面加载个数的一半（实际数量是此数量的两倍）
        imageCacheCount: 50, // 图片缓存个数的一半（实际数量是此数量的两倍）
        imageCachingList: {}, // 正在缓存的图片列表

        layer: null,

        stageLoaded: false,

        currentLeft: 0,
        currentTop: 0,
        currentRotation: 0,

        scaleType: 'page', // 缩放类型
        scale: 1, // 缩放比例
        minScale: 1, // 缩放的最小比例
        maxScale: 4, // 缩放的最大比例

        pointerType: '',
        isOverFlag: false,

        containerSize: {
          width: 0,
          height: 0
        },

        dx: 0, // 舞台横坐标与屏幕横坐标的差值
        dy: 0 // 舞台纵坐标与屏幕纵坐标的差值
      }
    },
    computed: {
      container () {
        return document.getElementById(`container${this.ref}`)
      },
      inBox () {
        return this.$refs.inBox
      },
      toolbox () {
        return this.$refs.toolbox
      },
      vScrollbar () {
        return this.$refs.vScrollbar
      },
      hScrollbar () {
        return this.$refs.hScrollbar
      },
      clipToolbarBox () {
        return this.$refs.clipToolbar
      },
      containerStyle () {
        let style = {width: '100%', height: '100%'}
        if (this.scrollbar.h) style.height = 'calc(100% - 8px)'
        if (this.scrollbar.v) style.width = 'calc(100% - 8px)'
        return style
      },

      totalCount () {
        return this.dataList.length
      },
      totalHeight () {
        if (!this.layer) return 0
        let totalHeight = this.imageSize.height * this.totalCount - this.containerSize.height / this.scale
        return totalHeight > 0 ? totalHeight : 0
      },
      totalWidth () {
        if (!this.layer) return 0

        let w = this.imageSize.width
        if ((this.currentRotation / 90) % 2) {
          w = this.imageSize.height
        }
        let totalWidth = w - this.containerSize.width / this.scale

        return totalWidth > 0 ? totalWidth : 0
      },
      progress () {
        let progress = this.totalHeight ? this.currentTop / this.totalHeight : 0
        this.$emit('progress', {progress})
        return progress
      },
      currentPage () {
        let delta = this.containerSize.height / this.scale * 0.5 + this.currentTop
        if (!this.imageSize.height) return 0
        let pageIndex = 0
        while ((delta -= this.imageSize.height) > 0) pageIndex++
        // log(delta)
        return pageIndex
      },
      maskBound () {
        let maskBound = getPos(this.container)

        maskBound.x = maskBound.x + (maskBound.width - this.imageSize.width * this.scale) / 2
        maskBound.y = maskBound.y + (this.imageSize.height * this.scale - maskBound.height) / 2

        maskBound.width = this.imageSize.width * this.scale
        maskBound.height = this.imageSize.height * this.scale

        return maskBound
      }
    },
    mounted () {
      this.pointerType = 'select'
      if (this.$scopedSlots.toolbox) {
        this.toolbox.style.display = 'block'
        this.inBox.style.height = `calc(100% - 30px)`
      }
    },
    watch: {
      totalHeight () {
        this.updateCurrentTop()
      },
      totalWidth () {
        this.updateCurrentLeft()
      },
      currentTop () {
        this.updateCurrentTop()
      },
      currentLeft () {
        this.updateCurrentLeft()
      },
      currentRotation () {
        if (this.currentRotation < 0) {
          return (this.currentRotation += 360)
        } else if (this.currentRotation >= 360) return (this.currentRotation -= 360)
        this.updatePages()
      },
      pointerType () {
        this.updateCursorState()
      },
      isOverFlag () {
        this.updateCursorState()
      },
      isClip () {
        this.updateCursorState()
      },
      markShow () {
        this.updateMarkListState()
      }
    },
    methods: {
      async skipPage (pageIndex) {
        if (!pageIndex < 0 || pageIndex > this.totalCount - 1) throw new Error('页码索引越界，请检查')
        try {
          this.loading = true
          this.currentTop = this.imageSize.height * pageIndex
          await this.cacheImages()

          await this.loadPages()
        } finally {
          this.loading = false
        }
      },
      updateScrollbarState () {
        this.scrollbar.v = this.totalHeight > 0
        this.scrollbar.h = this.totalWidth > 0
      },
      switchCursorTool (type) {
        this.pointerType = type
      },
      copySelectText () {
        let selection = this.getSelection()
        let tempEl = document.createElement('textarea')
        tempEl.value = selection.text
        tempEl.display = 'none'
        document.body.append(tempEl)
        tempEl.select()
        document.execCommand('copy')
        tempEl.parentElement.removeChild(tempEl)
      },
      toggleMarkShow () {
        this.markShow = !this.markShow
      },
      updateMarkListState () {
        if (this.markShow) {
          this.drawMarkList()
        } else {
          this.clearSelection([], 'mark')
        }
      },
      drawMarkList () {
        this.markList.map(item => {
          if (/^(\d+)-(\d+)$/.test(item.start) && /^(\d+)-(\d+)$/.test(item.start)) {
            // 文字选区
            let A = this.getWordByIndexPos(item.start)
            let B = this.getWordByIndexPos(item.end)

            this.drawSelection(this.getWordsAToB(A, B), item)
          } else {
            // 指纹印章，图片等
            this.drawRect(item)
          }
        })
      },
      /* 根据文字索引坐标获取文字 */
      getWordByIndexPos (pos) {
        if (/^(\d+)-(\d+)$/.test(pos)) {
          let pageIndex = Number(RegExp.$1)
          let index = Number(RegExp.$2)

          let data = this.dataList.find(item => item.page === pageIndex)
          return data.text.words[index]
        }
      },
      drawRect (selection) {
        let size = JSON.parse(selection.end)

        let page = this.layer.findOne(`.${selection.start}`)
        if (!page) return

        let config = {
          ...size,
          listening: false,
          name: `mark ${selection.markid}`,
          markid: selection.markid,
          strokeWidth: 5,
          fill: 'transparent',
          stroke: 'transparent'
        }

        if (selection.color) config.fill = selection.color
        if (selection.stroke) config.stroke = selection.stroke

        let rect = new Konva.Rect(config)
        page.add(rect)
        this.drawFlag({markid: selection.markid, x: config.x + config.width, y: config.y}, page)
        this.layer.draw()
      },
      drawFlag ({markid, x, y}, page) {
        let clone = flagImg.clone({
          id: `flag_${markid}`,
          x: x + 30,
          y: y
        })
        clone.on('click mouseover mouseleave', evt => {
          switch (evt.type) {
            case 'click':
              this.$emit('flag-click', {markid})
              break
            case 'mouseover':
              this.isOverFlag = true
              this.$emit('flag-mouseover', {markid})
              break
            case 'mouseleave':
              this.isOverFlag = false
              this.$emit('flag-mouseleave', {markid})
              break
          }
        })
        page.add(clone)
      },
      /* 更新currentTop */
      updateCurrentTop () {
        if (this.currentTop < 0) {
          this.currentTop = 0
        } else if (this.currentTop > this.totalHeight) this.currentTop = this.totalHeight

        this.updatePages()
        this.updateScrollbarState()
      },
      /* 更新currentLeft */
      updateCurrentLeft () {
        if (this.currentLeft < -this.totalWidth / 2) {
          this.currentLeft = -this.totalWidth / 2
        } else if (this.currentLeft > this.totalWidth / 2) this.currentLeft = this.totalWidth / 2

        this.updatePages()
        this.updateScrollbarState()
      },
      /* 容器尺寸大小变化 */
      handleSizeChange () {
        log('容器大小改变')
        if (!this.layer) return

        this.layer.getStage().setAttrs({
          width: this.containerSize.width,
          height: this.containerSize.height
        })

        let minScaleHeight = this.totalHeight > 1 ? this.containerSize.height / this.imageSize.height / 2 : this.containerSize.height / this.imageSize.height
        this.minScale = Math.min(minScaleHeight, this.containerSize.width / this.imageSize.width)

        this.setScale()
      },
      /* 调整进度 */
      regulateProgress (type, evt) {
        let startX = evt.screenX
        let startY = evt.screenY
        let currentTop = this.currentTop
        let currentLeft = this.currentLeft

        const mousemove = ev => {
          let deltaX = ev.screenX - startX
          let deltaY = ev.screenY - startY

          if (type === 'v') {
            this.currentTop = currentTop + deltaY / (this.vScrollbar.offsetHeight / this.totalHeight)
          } else if (type === 'h') {
            this.currentLeft = currentLeft + deltaX / (this.hScrollbar.offsetWidth / this.totalWidth)
          }
        }
        const mouseup = ev => {
          window.removeEventListener('mousemove', mousemove)
          window.removeEventListener('mouseup', mouseup)
        }

        window.addEventListener('mouseup', mouseup)
        window.addEventListener('mousemove', mousemove)
      },

      /* 初始化舞台 */
      initStage ({height, width}) {
        if (this.stageLoaded) return
        log('初始化舞台')
        this.imageSize.width = width
        this.imageSize.height = height

        const stage = new Konva.Stage({container: `container${this.ref}`})
        this.layer = new Konva.Layer()
        stage.add(this.layer)

        stage.preventDefault(true)
        stage.on('wheel', ({evt}) => {
          this.handleStageWheel(evt)
        })
        stage.on('mousedown', ({evt}) => {
          if (this.isClip) return
          this.handleStageMousedown(evt)
        })
        stage.on('contextmenu', ({evt}) => {
          if (this.isClip) return
          let shape = stage.getIntersection(this.getPositionOnStage())
          if (!shape || !shape.hasName('selection')) this.clearSelection()

          let selection = this.getSelection()
          this.$emit('marking-contextmenu', {selection, evt})
        })

        this.stageLoaded = true

        /* eslint-disable no-new */
        new MyResizeObserver(this.container, (arg) => {
          this.containerSize.width = arg.width
          this.containerSize.height = arg.height
          this.handleSizeChange()
        })
      },

      /* 舞台鼠标滚轮事件 */
      handleStageWheel (evt) {
        evt.preventDefault()
        evt.stopPropagation()
        if (this.isClip) return
        if (evt.ctrlKey) {
          if (evt.deltaY > 0) {
            this.zoomOut()
          } else {
            this.zoomIn()
          }
          return
        }
        if (this.totalHeight < 0) return
        if (evt.deltaY > 0) {
          this.currentTop += 100 / this.scale
        } else {
          this.currentTop -= 100 / this.scale
        }
      },
      /* 舞台鼠标按下事件 */
      handleStageMousedown (evt) {
        if (evt.button) return
        // evt.preventDefault()
        // evt.stopPropagation()
        // log(evt)

        this.clearSelection()
        let pos = this.layer.getStage().getPointerPosition()
        this.dx = pos.x - evt.screenX
        this.dy = pos.y - evt.screenY

        let startX = evt.screenX
        let startY = evt.screenY
        let currentTop = this.currentTop
        let currentLeft = this.currentLeft

        let deltaX = 0
        let deltaY = 0

        let A
        if (this.pointerType === 'hand') {
          this.container.style.cursor = 'grabbing'
        } else if (this.pointerType === 'select') {
          A = this.getWordByPointerPositionOnStage()
          log('获取文字')
        }

        const mousemove = ev => {
          if (this.pointerType === 'hand') {
            deltaX = ev.screenX - startX
            deltaY = ev.screenY - startY
            this.currentTop = currentTop - deltaY / this.scale
            this.currentLeft = currentLeft - deltaX / this.scale
          } else if (this.pointerType === 'select') {
            let B = this.getWordByPointerPositionOnStage()
            if (!B) return
            if (!A) A = B
            this.drawSelection(this.getWordsAToB(A, B))

            if (this.getPositionOnStage().y >= this.layer.height() - 30) {
              this.currentTop += 10 / this.scale
            } else if (this.getPositionOnStage().y <= 30) {
              this.currentTop -= 10 / this.scale
            }
            if (this.getPositionOnStage().x >= this.layer.width() - 30) {
              this.currentLeft += 10 / this.scale
            } else if (this.getPositionOnStage().x <= 30) {
              this.currentLeft -= 10 / this.scale
            }
          }
        }
        const mouseup = ev => {
          window.removeEventListener('mouseup', mouseup)
          window.removeEventListener('mousemove', mousemove)
          if (this.pointerType === 'hand') {
            this.container.style.cursor = 'grab'
          } else if (this.pointerType === 'select') {
            let selection = this.getSelection()
            if (!selection) return
            this.$emit('selection-change', {selection, evt: ev})
          }
        }
        window.addEventListener('mouseup', mouseup)
        window.addEventListener('mousemove', mousemove)
      },
      /* 获取选区内容 */
      getSelection () {
        let attrs
        let s = this.layer.find('.selection').map(child => {
          attrs = child.getAttrs()
          if (/(\d+)_(\d+)/.test(attrs.id)) {
            return {
              pageIndex: Number(RegExp.$1),
              rowIndex: Number(RegExp.$2),
              start: attrs.start,
              end: attrs.end,
              text: attrs.text
            }
          }
        })

        s.sort((a, b) => a.pageIndex === b.pageIndex ? a.rowIndex - b.rowIndex : a.pageIndex - b.pageIndex)

        let res = s.reduce((res, item, index) => {
          if (!index) res.start = item.start
          if (index === s.length - 1) res.end = item.end
          if (index) res.text += '\n'
          res.text += item.text

          return res
        }, {text: ''})

        return res.text ? res : null
      },
      /* 绘制选区 */
      drawSelection (selections, {markid, color, stroke} = {}) {
        let tid = markid || 'selection'
        for (let selectionName in selections) {
          selections[`${tid}_${selectionName}`] = selections[selectionName]
          delete selections[selectionName]
        }

        let names = Object.keys(selections)
        this.clearSelection(names)

        names.map((name, index) => {
          let selection = this.layer.findOne(`#${name}`)
          if (selection) {
            selection.setAttrs({
              x: selections[name].x,
              y: selections[name].y,
              width: selections[name].width,
              height: selections[name].height,

              text: selections[name].text,
              start: selections[name].start,
              end: selections[name].end
            })
          } else {
            let page = this.layer.findOne(`.${selections[name].page}`)
            if (!page) return
            let config = {
              x: selections[name].x,
              y: selections[name].y,
              width: selections[name].width,
              height: selections[name].height,
              fill: 'transparent',
              stroke: 'transparent',
              // listening: false,
              draggable: false,
              id: name,

              text: selections[name].text,
              start: selections[name].start,
              end: selections[name].end
            }

            if (markid) {
              config.name = `mark`
              config.markid = markid
              config.strokeWidth = 5
              config.listening = false
              if (color) config.fill = color
              if (stroke) config.stroke = stroke

              if (index === names.length - 1) {
                this.drawFlag({
                  markid,
                  x: selections[name].x + selections[name].width,
                  y: selections[name].y
                }, page)
              }
            } else {
              config.fill = this.selectBgColor
              config.name = 'selection'
            }

            selection = new Konva.Rect(config)
            page.add(selection).draw()
          }
        })
        this.layer.draw()
      },
      /* 清除选区 */
      clearSelection (ids = [], className = 'selection') {
        // ids 为要保留的id数组，为空即全部清除
        this.layer.find(`.${className}`).map(child => {
          if (ids.indexOf(child.id()) === -1) {
            let markid = child.getAttr('markid')
            if (markid) {
              let flag = this.layer.findOne(`#flag_${markid}`)
              if (flag) flag.destroy()
            }
            child.destroy()
          }
        })
        this.layer.draw()
      },
      /* 获取指针在舞台中的位置 */
      getPositionOnStage () {
        return {
          x: this.dx + event.screenX,
          y: this.dy + event.screenY
        }
      },
      /* 通过指针位置获取文字 */
      getWordByPointerPositionOnStage () {
        let positionOnStage = this.getPositionOnStage()

        let delta = positionOnStage.y + this.currentTop * this.scale
        // 计算当前点击位置是第几页
        let pageIndex = 0 // 目前光标所在页索引
        while ((delta -= this.imageSize.height * this.scale) > 0) pageIndex++

        let data = this.dataList.find(item => item.page === pageIndex)
        if (!data) return
        let text = data.text
        if (!text) return
        if (!text.words) return
        if (!text.rows) return

        let cx = -this.currentLeft
        let cy = -this.currentTop + this.imageSize.height * pageIndex + this.imageSize.height / 2
        let cr = this.currentRotation
        if (cr === 90) {
          cx = cy
          cy = this.currentLeft
        } else if (cr === 180) {
          cy = -cy
        } else if (cr === 270) {
          cx = -cy
          cy = -this.currentLeft
        }
        cr = cr * Math.PI / 180

        let tx = positionOnStage.x - this.layer.width() / 2
        let ty = positionOnStage.y

        let x = tx * Math.cos(cr) + ty * Math.sin(cr)
        let y = -tx * Math.sin(cr) + ty * Math.cos(cr)

        let positionOnImage = {
          x: x / this.scale + this.imageSize.width / 2 - cx,
          y: y / this.scale + this.imageSize.height / 2 - cy
        }

        let prevRowIndex = -1
        let minLineHeight = this.imageSize.height
        for (let word of text.words) {
          // 行内
          if (positionOnImage.y >= word.y1 && positionOnImage.y <= word.y2) {
            prevRowIndex = word.rowIndex
            break
          } else {
            // 判断最小间隙
            if (prevRowIndex !== word.rowIndex) {
              // 换行了
              if (positionOnImage.y - word.y2 > 0) {
                // 鼠标上面
                if (positionOnImage.y - word.y2 < minLineHeight) {
                  minLineHeight = positionOnImage.y - word.y2
                  prevRowIndex = word.rowIndex
                }
              } else if (word.y1 - positionOnImage.y > 0) {
                // 鼠标下面
                if (word.y1 - positionOnImage.y < minLineHeight) {
                  minLineHeight = word.y1 - positionOnImage.y
                  prevRowIndex = word.rowIndex
                }
              }
            }
          }
        }
        // 文字行prevRowIndex
        let words = text.rows[prevRowIndex] || []
        for (let word of words) {
          if (positionOnImage.x < word.x1 && word.wordIndex === 0) {
            // log('最左边', word)
            return {
              position: 'left',
              ...word
            }
          } else if (positionOnImage.x > word.x2 && word.wordIndex === text.rows[prevRowIndex].length - 1) {
            // log('最右边', word)
            return {
              position: 'right',
              ...word
            }
          } else if (positionOnImage.x >= word.x1 && positionOnImage.x <= word.x2) {
            // log('字上面', word)
            return {
              position: positionOnImage.x > (word.x1 + word.x2) / 2 ? 'right' : 'left',
              ...word
            }
          }
        }
      },
      /* 获取两字之间的文字 */
      getWordsAToB (A, B) {
        if (A.pageIndex > B.pageIndex) {
          [A, B] = [B, A]
        } else if (A.pageIndex === B.pageIndex) {
          if (A.index > B.index) {
            [A, B] = [B, A]
          }
        }
        let tRows = {}

        // AB之间的页
        for (let i = A.pageIndex; i <= B.pageIndex; i++) {
          let cache = this.dataList.find(item => item.page === i)
          let text = cache.text
          let start = 0
          let end = text.words.length - 1
          if (i === A.pageIndex && i === B.pageIndex) {
            start = A.index
            end = B.index
          } else if (i === A.pageIndex) {
            start = A.index
          } else if (i === B.pageIndex) {
            end = B.index
          }
          for (let j = start; j <= end; j++) {
            let word = text.words[j]

            if (B.pageIndex === A.pageIndex && B.index === A.index) {
              if (A.position === B.position) continue
            } else {
              if (word.pageIndex === A.pageIndex && word.index === A.index) {
                if (A.position === 'right') continue
              }
              if (word.pageIndex === B.pageIndex && word.index === B.index) {
                if (B.position === 'left') continue
              }
            }

            let name = `${word.pageIndex}_${word.rowIndex}`
            if (!tRows[name]) {
              tRows[name] = {
                page: word.pageIndex,
                x: word.x1,
                y: word.y1,
                width: 0,
                height: 0,
                text: '',
                start: `${word.pageIndex}-${word.index}`,
                end: ''
              }
            }
            tRows[name].width = word.x2 - tRows[name].x
            tRows[name].height = word.y2 - tRows[name].y
            tRows[name].text += word.text
            tRows[name].end = `${word.pageIndex}-${word.index}`
          }
        }
        return tRows
      },

      /* 加载及打开页面 */
      reload () {
        this.load()
      },
      async load (arg = this.loadArg) {
        this.loadArg = arg
        this.loading = true

        if (typeof this.loadFn !== 'function') throw new Error('调用load方法必须绑定load-fn参数')

        let data = await this.loadFn(this.loadArg)

        this.open(data)
      },
      async open (arg) {
        this.loading = true
        this.stageLoaded = false

        try {
          this.dataList = arg.data
          this.markList = arg.markList

          this.currentLeft = 0
          this.currentTop = 0
          this.imageList = {}

          if (arg.pageIndex) {
            await this.skipPage(arg.pageIndex)
          } else {
            await this.cacheImages()

            await this.loadPages()
          }

          this.updateMarkListState()
        } catch (e) {
          console.error('打开错误', e.message)
        } finally {
          this.loading = false
        }
      },

      /* 缓存图片数据 */
      async cacheImages () {
        try {
          log('开始缓存图片数据')
          // 计算需要缓存的图片索引
          let end = this.currentPage + this.imageCacheCount
          let start = this.currentPage - this.imageCacheCount

          if (start < 0) start = 0
          if (end > this.totalCount) end = this.totalCount

          // 删除缓存长度以外的图片
          for (let index in this.imageList) {
            if (index < start || index > end) delete this.imageList[index]
          }

          // 缓存需要的图片
          for (let i = start; i < end; i++) {
            // 如果已缓存，则进入下一次循环
            if (this.imageList[i]) continue
            // 如果正在缓存，则进入下一次循环
            if (this.imageCachingList[i]) continue
            // 标记为正在缓存
            this.imageCachingList[i] = true

            log('缓存图片', i)

            let imgSorce = await this.cacheImage(i)

            delete this.imageCachingList[i]

            this.imageList[i] = imgSorce
          }
          log('图片数据缓存结束')
        } catch (e) {
          console.error('缓存图片出现错误', e.message)
        }
      },
      cacheImage (pageIndex) {
        return new Promise(async (resolve, reject) => {
          let img = new Image()
          let src = this.dataList[pageIndex].img
          if (this.imageLoadFn && typeof this.imageLoadFn === 'function') src = await this.imageLoadFn(src)
          img.onload = () => {
            this.initStage({width: img.width, height: img.height})
            resolve(img)
          }
          img.src = src
        })
      },

      /* 加载页面 */
      async loadPages () {
        if (!this.stageLoaded) throw new Error('舞台尚未初始化')
        try {
          log('开始加载页面')
          let end = this.currentPage + this.pageLoadCount
          let start = this.currentPage - this.pageLoadCount

          if (start < 0) start = 0
          if (end > this.totalCount) end = this.totalCount

          let page
          for (let i = 0; i < this.totalCount; i++) {
            page = this.layer.find(`.${i}`)
            if (i >= start && i < end) {
              if (page.length === 0) {
                // 新建页面
                log('加载页面', i)
                await this.loadPage(i)
              } else if (page.length > 1) {
                // 删除多余页面
              }
            } else {
              // 删除页面
            }
          }
          log('页面加载完成')
        } catch (e) {
          console.error('加载页面出现错误', e.message)
        }
      },
      async loadPage (pageIndex) {
        if (this.layer.findOne(`.${pageIndex}`)) return
        let img = this.imageList[pageIndex]
        if (!img) return
        let page = new Konva.Group({
          x: -this.currentLeft,
          y: -this.currentTop + this.imageSize.height * pageIndex + this.imageSize.height / 2,
          offsetX: this.imageSize.width / 2,
          offsetY: this.imageSize.height / 2,
          width: this.imageSize.width,
          draggable: false,
          height: this.imageSize.height,
          rotation: this.currentRotation,
          name: `${pageIndex}`
        })
        let pageImage = new Konva.Image({
          x: 0,
          y: 0,
          image: img,
          draggable: false,
          width: this.imageSize.width,
          height: this.imageSize.height
        })
        page.add(pageImage)

        if (this.watermark.count) {
          if (this.watermark.text) {
            for (let i = 0; i < this.watermark.count; i++) {
              let watermark = new Konva.Text({
                x: (this.watermark.x || 0),
                y: this.imageSize.height * (i + 1) / this.watermark.count - (this.watermark.fontSize || 200) - (this.watermark.y || 0),
                rotation: this.watermark.rotation || -45,
                text: this.watermark.text,
                fontSize: this.watermark.fontSize || 200,
                fill: this.watermark.color || 'rgba(0,0,0,0.15)',
                listening: false,
                draggable: false
              })
              console.log(watermark)
              page.add(watermark)
            }
          }
        }

        this.layer.add(page).draw()
      },
      async updatePages () {
        try {
          log('开始更新页面')
          let end = this.currentPage + this.pageLoadCount
          let start = this.currentPage - this.pageLoadCount
          for (let i = start; i < end; i++) {
            let page = this.layer.findOne(`.${i}`)
            if (page) {
              page.setAttrs({
                x: -this.currentLeft,
                y: -this.currentTop + this.imageSize.height * i + this.imageSize.height / 2,
                rotation: this.currentRotation
              })
              this.layer.draw()
            }
          }
          this.cacheImages()
          this.loadPages()
        } catch (e) {
          console.error('更新页面出现错误', e.message)
        }
      },

      /* 页面缩放 */
      setScale (scaleType = this.scaleType) {
        log('设置缩放比例')
        this.scaleType = scaleType
        this.scale = scaleType
        // original page-fit page
        if (scaleType === 'original') {
          // 原始大小
          this.scale = 1
        } else if (scaleType === 'page-fit') {
          // 适合页宽
          this.scale = this.containerSize.width / this.imageSize.width
        } else if (scaleType === 'page') {
          // 适合页面
          this.scale = Math.min(this.containerSize.height / this.imageSize.height, this.containerSize.width / this.imageSize.width)
        }

        if (this.scale > this.maxScale) {
          this.scale = this.maxScale
        } else if (this.scale < this.minScale) this.scale = this.minScale
        this.layer.getStage().setAttrs({
          offsetX: -this.containerSize.width / this.scale / 2,
          scale: {
            x: this.scale,
            y: this.scale
          }
        })
        this.layer.draw()
      },
      zoomOut () {
        log('缩小')
        let scale = this.scale
        scale -= 0.1
        this.setScale(scale)
      },
      zoomIn () {
        log('放大')
        let scale = this.scale
        scale += 0.1
        this.setScale(scale)
      },

      /* 页面旋转 */
      clockwise () {
        log('顺时针旋转')
        this.currentRotation += 90
      },
      antiClockwise () {
        log('逆时针旋转')
        this.currentRotation -= 90
      },

      getLayerImage () {
        return new Promise((resolve, reject) => {
          let img = new Image()
          img.onload = ev => resolve(img)
          img.src = this.layer.toDataURL()
        })
      },

      /* 截图 */
      async clip () {
        if (this.layer.findOne('#clipMask')) return
        this.setScale('page')
        this.isClip = true
        this.$emit('clip-state-change', {state: 'clip'})

        let maskWidth = this.imageSize.width
        let maskHeight = this.imageSize.height

        let img = await this.getLayerImage()

        // 创建一个group，用于盛放截图遮罩和截图显示区域

        let clipMask = new Konva.Rect({
          id: 'clipMask',
          offset: {x: maskWidth / 2},
          width: maskWidth,
          height: maskHeight,
          x: 0,
          y: 0,
          // data: `m-${0},-${0}
          //       l0,${maskHeight}
          //       l${maskWidth},0
          //       l0,-${maskHeight}
          //       l-${maskWidth},0z`,
          fill: 'rgba(0,0,0,0.7)'
        })

        let clipImage
        clipMask.on('mousedown', (evt) => {
          evt.cancelBubble = true
          if (evt.evt.button) return
          if (clipImage) return
          this.hideClipToolbarBox()

          let pos = this.layer.getStage().getPointerPosition()
          this.dx = pos.x - evt.evt.screenX
          this.dy = pos.y - evt.evt.screenY

          let deltaX = 0
          let deltaY = 0

          let x = pos.x / this.scale - this.layer.width() / this.scale / 2
          let y = pos.y / this.scale

          let newX
          let newY

          const mousemove = ev => {
            let positionOnStage = this.getPositionOnStage()
            deltaX = (positionOnStage.x - pos.x) / this.scale
            deltaY = (positionOnStage.y - pos.y) / this.scale

            newX = x
            newY = y

            if (deltaX < 0) {
              deltaX = Math.abs(deltaX)
              newX -= deltaX
            }
            if (deltaY < 0) {
              deltaY = Math.abs(deltaY)
              newY -= deltaY
            }

            if (clipImage) {
              clipImage.setAttrs({
                x: newX,
                y: newY,
                width: deltaX,
                height: deltaY,
                crop: {
                  x: newX * this.scale + this.layer.width() / 2,
                  y: newY * this.scale,
                  width: deltaX * this.scale,
                  height: deltaY * this.scale
                }
              })
            } else {
              clipImage = new Konva.Image({
                id: 'clipImage',
                x: x,
                y: y,
                image: img,
                stroke: 'red',
                strokeWidth: 5,
                draggable: true,
                dragBoundFunc: pos => {
                  clipImage.setAttr('crop', {x: pos.x, y: pos.y})
                  return pos
                }
              })

              clipImage.on('dragstart', this.hideClipToolbarBox)
              clipImage.on('dragend', this.showClipToolbarBox)
              clipImage.on('mouseenter', () => (this.container.style.cursor = 'move'))
              clipImage.on('mouseleave', this.updateCursorState)

              this.layer.add(clipImage)
            }
            this.layer.draw()
          }

          const mouseup = () => {
            window.removeEventListener('mouseup', mouseup)
            window.removeEventListener('mousemove', mousemove)
            this.showClipToolbarBox()
          }
          window.addEventListener('mouseup', mouseup)
          window.addEventListener('mousemove', mousemove)
        })
        this.layer.add(clipMask).draw()
      },
      showClipToolbarBox () {
        let clipImage = this.layer.findOne('#clipImage')
        if (!clipImage) return

        let x = clipImage.x()
        let y = clipImage.y()
        let w = clipImage.width()
        let h = clipImage.height()

        this.clipToolbarBox.style.left = `${(x + w) * this.scale + this.layer.width() / 2 - this.clipToolbarBox.offsetWidth}px`
        this.clipToolbarBox.style.top = `${(y + h) * this.scale}px`
        this.clipToolbarBox.style.visibility = 'visible'
      },
      hideClipToolbarBox () {
        this.clipToolbarBox.style.visibility = 'hidden'
      },
      updateCursorState () {
        if (this.isClip) {
          this.container.style.cursor = 'crosshair'
        } else if (this.isOverFlag) {
          this.container.style.cursor = 'default'
        } else if (this.pointerType === 'hand') {
          this.container.style.cursor = 'grab'
        } else if (this.pointerType === 'select') {
          this.container.style.cursor = 'text'
        }
      },
      exitClip () {
        let clipImage = this.layer.findOne('#clipImage')
        if (clipImage) clipImage.destroy()
        let clipMask = this.layer.findOne('#clipMask')
        if (clipMask) clipMask.destroy()
        this.layer.draw()
        this.isClip = false
        this.hideClipToolbarBox()
      },
      cancelClip () {
        log('取消截图')
        this.$emit('clip-state-change', {state: 'cancel'})
      },
      completeClip (ev, data) {
        log('完成截图')
        this.$emit('clip-state-change', {state: 'complete', data})
      },
      handleClipToolItemClick (item, ev) {
        let data
        let clipImage = this.layer.findOne('#clipImage')
        if (clipImage) {
          clipImage.setAttr('strokeWidth', 0)
          let imgData = clipImage.toDataURL()
          data = {
            src: imgData,
            x: clipImage.x() + this.imageSize.width / 2,
            y: clipImage.y(),
            width: clipImage.width(),
            height: clipImage.height(),
            page: this.currentPage
          }
        }

        if (item.click) item.click(ev, data)
        this.exitClip()
      }
    },
    props: {
      loadFn: Function,
      imageLoadFn: Function,
      selectBgColor: {
        type: String,
        default: 'rgba(0,180,255,0.48)'
      },
      clipToolbar: {
        type: Array,
        default: () => []
      },
      watermark: {
        type: Object,
        default: () => {
          return {
            text: '',
            count: 3
          }
        }
      }
    }
  }
</script>

<style scoped lang="scss">
  .out-box {
    height: 100%;
    width: 100%;
    position: relative;
    user-select: none;
    cursor: default;

    .toolbox {
      height: 30px;
      width: 100%;
      overflow: hidden;
      cursor: default;
      position: relative;
      border-bottom: 1px solid #ccc;
      box-sizing: border-box;
      display: none;
    }

    .in-box {
      height: 100%;
      width: 100%;
      position: relative;

      .container {
        cursor: grab;
        /*background-color: #f00;*/
        overflow: hidden;
      }

      .scrollbar {
        position: absolute;
        background-color: #cccccc;
        border-radius: 4px;

        &.v-scrollbar {
          top: 0;
          right: 0;
          bottom: 58px;
          /*height: 50px;*/
          width: 8px;

          &::after {
            content: ' ';
            width: 100%;
            height: 60px;
            background-color: #cccccc;
            position: absolute;
            bottom: -50px;
            border-radius: 4px;
          }
        }

        &.h-scrollbar {
          bottom: 0;
          left: 0;
          right: 58px;
          /*width: 50px;*/
          height: 8px;

          &::after {
            content: ' ';
            height: 100%;
            width: 60px;
            background-color: #cccccc;
            position: absolute;
            right: -50px;
            border-radius: 4px;
          }
        }


        .thumb {
          position: absolute;
          background-color: #2f7cb9;
          border-radius: 4px;
          z-index: 2;

          &.v-thumb {
            width: 100%;
            height: 50px;

            &::before {
              content: attr(tip);
              position: absolute;
              top: 50%;
              transform: translateY(-50%);
              width: max-content;
              display: block;
              font-size: 12px;
              right: 14px;
              background-color: white;
              padding: 2px 6px;
              color: black;
              border: 1px solid #ccc;
              border-radius: 3px;
              z-index: 1;
              box-sizing: border-box;
              pointer-events: none;
            }

            &::after {
              content: ' ';
              font-size: 0;
              position: absolute;
              top: 50%;
              transform: translateY(-50%) rotateZ(45deg);
              background-color: white;
              right: 10px;
              height: 10px;
              width: 10px;
              border: 1px solid;
              border-color: #ccc #ccc transparent transparent;
              z-index: 1;
              box-sizing: border-box;
            }
          }

          &.h-thumb {
            height: 100%;
            width: 50px;
          }
        }
      }

      .page-mask {
        position: fixed;
        top: 0;
        right: 0;
        bottom: 0;
        left: 0;
        z-index: 999999999999;
        pointer-events: none;

        .page-mask-item {
          position: fixed;
          background-color: rgba(0, 0, 0, 0.5);
          pointer-events: all;

          &.top {
            left: 0;
            top: 0;
          }

          &.right {
            top: 0;
            right: 0;
          }

          &.bottom {
            bottom: 0;
            right: 0;
          }

          &.left {
            bottom: 0;
            left: 0;
          }
        }

      }

      .clip-tool {
        position: absolute;
        top: 0;
        left: 0;
        background-color: white;
        border: 1px solid #ccc;
        border-radius: 3px;
        padding: 0 3px;
        box-sizing: border-box;
        visibility: hidden;

        .clip-tool-item {
          display: inline-block;
          padding: 0 5px;
          margin: 3px 0;

          &:hover {
            background-color: rgba(204, 204, 204, 0.5);
          }
        }
      }
    }

    .loading-mask {
      position: absolute;
      top: 0;
      left: 0;
      right: 0;
      bottom: 0;
      background-color: rgba(0, 0, 0, 0.2);

      .circular {
        height: 75px;
        width: 75px;
        animation: loading-rotate 2s linear infinite;

        position: absolute;
        left: 0;
        right: 0;
        top: 0;
        bottom: 0;
        margin: auto;

        .path {
          animation: loading-dash 1.5s ease-in-out infinite;
          stroke-dasharray: 90, 150;
          stroke-dashoffset: 0;
          stroke-width: 2;
          stroke: #409EFF;
          stroke-linecap: round;
        }
      }


      @keyframes loading-rotate {
        100% {
          -webkit-transform: rotate(360deg);
          transform: rotate(360deg)
        }
      }
      @keyframes loading-dash {
        0% {
          stroke-dasharray: 1, 200;
          stroke-dashoffset: 0
        }
        50% {
          stroke-dasharray: 90, 150;
          stroke-dashoffset: -40px
        }
        100% {
          stroke-dasharray: 90, 150;
          stroke-dashoffset: -120px
        }
      }
    }
  }

</style>
